/**
 * Copyright (c) 2008, 2019 https://www.snakeyaml.org and others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Contributors:
 *
 *   2008 - https://www.snakeyaml.org original api and implementation.
 *   2019 - Copied and modified by Pivotal.
 */
package org.springframework.ide.vscode.commons.yaml.ast;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems;
import org.springframework.ide.vscode.commons.yaml.util.YamlUtil;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.events.Event;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeTuple;
import org.yaml.snakeyaml.nodes.SequenceNode;
import org.yaml.snakeyaml.nodes.Tag;

/**
 * Bits and pieces copied from snakeyaml library to support dealing with '<<' merge
 * nodes.
 * <p>
 * Some modifications made to support our own use-cases.
 */
public class NodeMergeSupport {

	private IProblemCollector problems;

	public NodeMergeSupport(IProblemCollector problems) {
		this.problems = problems;
	}

	public void flattenMapping(Node node) {
		if (node instanceof MappingNode) {
			flattenMapping((MappingNode)node);
		}
	}

	private void flattenMapping(MappingNode node) {
		// perform merging only on nodes containing merge node(s)
		//processDuplicateKeys(node);

		if (node.isMerged()) {
			node.setValue(mergeNode(node, true, new HashMap<Object, Integer>(),
					new ArrayList<NodeTuple>()));
			node.setMerged(false);
		}
	}

	/**
	 * Does merge for supplied mapping node.
	 *
	 * @param node
	 *            where to merge
	 * @param isPreffered
	 *            true if keys of node should take precedence over others...
	 * @param key2index
	 *            maps already merged keys to index from values
	 * @param values
	 *            collects merged NodeTuple
	 * @return list of the merged NodeTuple (to be set as value for the
	 *         MappingNode)
	 */
	private List<NodeTuple> mergeNode(MappingNode node, boolean isPreffered,
			Map<Object, Integer> key2index, List<NodeTuple> values) {
		Iterator<NodeTuple> iter = node.getValue().iterator();
		while (iter.hasNext()) {
			final NodeTuple nodeTuple = iter.next();
			final Node keyNode = nodeTuple.getKeyNode();
			final Node valueNode = nodeTuple.getValueNode();
			if (keyNode.getTag().equals(Tag.MERGE)) {
				iter.remove();
				switch (valueNode.getNodeId()) {
				case mapping:
					MappingNode mn = (MappingNode) valueNode;
					mergeNode(mn, false, key2index, values);
					break;
				case sequence:
					SequenceNode sn = (SequenceNode) valueNode;
					List<Node> vals = sn.getValue();
					for (Node subnode : vals) {
						if (!(subnode instanceof MappingNode)) {
							problems.accept(YamlSchemaProblems.schemaProblem(
									"Expected a mapping for merging, but found "+subnode.getNodeId(), subnode
									));
						} else {
							MappingNode mnode = (MappingNode) subnode;
							mergeNode(mnode, false, key2index, values);
						}
					}
					break;
				default:
					problems.accept(YamlSchemaProblems.schemaProblem(
							"Expected a mapping or list of mappings for merging, but found "
									+ valueNode.getNodeId(),
									valueNode
							));
				}
			} else {
				// we need to construct keys to avoid duplications
				String key = NodeUtil.asScalar(keyNode);
				if (key!=null) {
					if (!key2index.containsKey(key)) { // 1st time merging key
						values.add(nodeTuple);
						// keep track where tuple for the key is
						key2index.put(key, values.size() - 1);
					} else if (isPreffered) { // there is value for the key, but we
						// need to override it
						// change value for the key using saved position
						values.set(key2index.get(key), nodeTuple);
					}
				}
			}
		}
		return values;
	}


}
